package com.gitee.urick.union.builder;

import com.gitee.urick.union.config.UnionConfig;
import com.gitee.urick.union.constants.UnionConstants;
import com.gitee.urick.union.constants.UnionTemplateVariable;
import com.gitee.urick.union.model.HtmlParser;
import com.gitee.urick.union.model.UnionApiDocGroup;
import com.google.gson.Gson;
import com.power.common.util.*;
import com.power.doc.builder.BaseDocBuilderTemplate;
import com.power.doc.constants.DocGlobalConstants;
import com.power.doc.constants.DocLanguage;
import com.power.doc.constants.HighlightStyle;
import com.power.doc.constants.TemplateVariable;
import com.power.doc.model.*;
import com.power.doc.utils.BeetlTemplateUtil;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import org.apache.commons.lang3.RegExUtils;
import org.beetl.core.Template;

import java.util.*;

import static com.power.doc.constants.DocGlobalConstants.FILE_SEPARATOR;
import static com.power.doc.constants.DocGlobalConstants.SEARCH_JS_OUT;
import static java.util.stream.Collectors.toList;

/**
 * @author Rick 2021/02/08
 * @since 1.0
 */
public class UnionDocDataBuilder extends BaseDocBuilderTemplate {
    private final static String REGEX_HEADER = "^[\\d.]+\\.+";
    private final long now = System.currentTimeMillis();
    private String commonDoc;
    private List<HtmlParser.HtmlToc> htmlToc;

    /**
     * init Data
     *
     * @param apiconfig global config
     */
    @Override
    public void checkAndInitForGetApiData(ApiConfig apiconfig) {
        super.checkAndInitForGetApiData(apiconfig);
        UnionConfig config = (UnionConfig) apiconfig;
        String commonDocPath = config.getCommonDocPath();
        if (StringUtil.isEmpty(commonDocPath)) return;
        commonDoc = FileUtil.getFileContent(commonDocPath);
        if (config.isHtml()) {
            HtmlParser tocParser = new HtmlParser(true);
            commonDoc = tocParser.parserHead(commonDoc);
            htmlToc = tocParser.getTocList();
        }
    }

    /**
     * build all One   api
     *
     * @param apiDocList         list of api doc
     * @param config             ApiConfig
     * @param javaProjectBuilder ProjectDocConfigBuilder
     * @param template           template
     */
    public void buildAllInOne(List<ApiDoc> apiDocList, UnionConfig config, JavaProjectBuilder javaProjectBuilder, String template, String outPutFileName) {
        String outPath = config.getOutPath();
        FileUtil.mkdirs(outPath);
        buildDoc(apiDocList, config, javaProjectBuilder, template, outPutFileName, commonDoc);
    }


    /**
     * @param apiDocList         docList
     * @param config             global config
     * @param javaProjectBuilder java resource scan builder
     * @param template           beelt tempplate
     * @param outPutFileName     out file name
     * @param docAbstract        doc Abstract
     */
    public void buildDoc(List<ApiDoc> apiDocList, UnionConfig config, JavaProjectBuilder javaProjectBuilder, String template, String outPutFileName, String docAbstract) {
        String strTime = DateTimeUtil.long2Str(now, DateTimeUtil.DATE_FORMAT_SECOND);
        List<ApiErrorCode> errorCodeList = errorCodeDictToList(config);
        Template tpl = BeetlTemplateUtil.getByName(template);
        String style = config.getStyle();
        tpl.binding(UnionTemplateVariable.DOC_ABSTRACT.getVariable(), docAbstract);
        tpl.binding(TemplateVariable.STYLE.getVariable(), style);
        tpl.binding(TemplateVariable.BACKGROUND.getVariable(), HighlightStyle.getBackgroundColor(style));
        tpl.binding(UnionTemplateVariable.DOC_DETAILS_ORDER.getVariable(), config.getStartOrder() + 1);
        tpl.binding(TemplateVariable.API_DOC_LIST.getVariable(), apiDocList);
        tpl.binding(TemplateVariable.ERROR_CODE_LIST.getVariable(), errorCodeList);
        tpl.binding(TemplateVariable.VERSION_LIST.getVariable(), config.getRevisionLogs());
        tpl.binding(UnionTemplateVariable.COPYRIGHT.getVariable(), config.getCopyright());
        tpl.binding(TemplateVariable.CREATE_TIME.getVariable(), strTime);
        tpl.binding(TemplateVariable.PROJECT_NAME.getVariable(), config.getProjectName());
        tpl.binding(TemplateVariable.REQUEST_EXAMPLE.getVariable(), config.isRequestExample());
        tpl.binding(TemplateVariable.RESPONSE_EXAMPLE.getVariable(), config.isResponseExample());
        if (CollectionUtil.isEmpty(errorCodeList)) {
            tpl.binding(TemplateVariable.DICT_ORDER.getVariable(), config.getStartOrder() + 2);
        } else {
            tpl.binding(UnionTemplateVariable.ERROR_ORDER.getVariable(), config.getStartOrder() + 2);
            tpl.binding(TemplateVariable.DICT_ORDER.getVariable(), config.getStartOrder() + 3);
        }
        setDirectoryLanguageVariable(config, tpl);
        List<ApiDocDict> apiDocDictList = buildDictionary(config, javaProjectBuilder);
        tpl.binding(TemplateVariable.DICT_LIST.getVariable(), apiDocDictList);
        tpl.binding(TemplateVariable.VERSION.getVariable(), now);
        tpl.binding(UnionTemplateVariable.HTML_TOC.getVariable(), htmlToc);
        FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + outPutFileName);
    }


    /**
     * @param apiDocList         docList
     * @param config             global config
     * @param javaProjectBuilder java resource scan builder
     * @param outDirectory       out directory
     */
    public void buildSingleDoc(List<ApiDoc> apiDocList, UnionConfig config, JavaProjectBuilder javaProjectBuilder, String outDirectory) {
        buildAbstractDoc(config, outDirectory);
        buildApiDoc(apiDocList, config, outDirectory);
        buildErrorCodeDoc(config, outDirectory);
        buildDictionaryDataDoc(config, javaProjectBuilder, outDirectory);
    }

    /**
     * @param config       global config
     * @param outDirectory out directory
     */
    private void buildAbstractDoc(UnionConfig config, String outDirectory) {
        Template tpl = BeetlTemplateUtil.getByName(outDirectory + UnionConstants.UNION_MD_ABSTRACT_TPL);
        String style = config.getStyle();
        tpl.binding(TemplateVariable.STYLE.getVariable(), style);
        tpl.binding(TemplateVariable.BACKGROUND.getVariable(), HighlightStyle.getBackgroundColor(style));
        tpl.binding(TemplateVariable.VERSION_LIST.getVariable(), config.getRevisionLogs());
        String commonDocPath = config.getCommonDocPath();
        String commonDoc = null;
        if (Objects.nonNull(commonDocPath)) {
            commonDoc = FileUtil.getFileContent(commonDocPath);
        }
        tpl.binding(UnionTemplateVariable.DOC_ABSTRACT.getVariable(), commonDoc);
        tpl.binding(UnionTemplateVariable.COPYRIGHT.getVariable(), config.getCopyright());
        tpl.binding(TemplateVariable.VERSION.getVariable(), now);
        String strTime = DateTimeUtil.long2Str(now, DateTimeUtil.DATE_FORMAT_SECOND);
        tpl.binding(TemplateVariable.CREATE_TIME.getVariable(), strTime);
        tpl.binding(TemplateVariable.PROJECT_NAME.getVariable(), config.getProjectName());
        FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + UnionConstants.UNION_MD_ABSTRACT_TPL);
    }

    /**
     * Generate api documentation for all handlers.
     *
     * @param apiDocList   list of api doc
     * @param config       api config
     * @param outDirectory out directory
     */
    private void buildApiDoc(List<ApiDoc> apiDocList, UnionConfig config, String outDirectory) {
        FileUtil.mkdirs(config.getOutPath());
        for (ApiDoc api : apiDocList) {
            UnionApiDocGroup apiDoc = (UnionApiDocGroup) api;
            Template tpl = BeetlTemplateUtil.getByName(outDirectory + UnionConstants.UNION_MD_TPL);
            String desc = StringUtil.firstToUpperCase(apiDoc.getName());
            tpl.binding(TemplateVariable.DESC.getVariable(), desc);
            tpl.binding(TemplateVariable.LIST.getVariable(), apiDoc.getList());
            String strTime = DateTimeUtil.long2Str(now, DateTimeUtil.DATE_FORMAT_SECOND);
            tpl.binding(TemplateVariable.CREATE_TIME.getVariable(), strTime);
            tpl.binding(TemplateVariable.VERSION.getVariable(), now);
            tpl.binding(UnionTemplateVariable.COPYRIGHT.getVariable(), config.getCopyright());
            if (apiDoc.isDefaultGroup()) {
                tpl.binding(UnionTemplateVariable.IS_DEFAULT_GROUP.getVariable(), Boolean.TRUE);
                FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + "Api.md");
            } else {
                FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + "Api-" + desc + ".md");
            }
        }
    }

    /**
     * build error_code markdown
     *
     * @param config       api config
     * @param outDirectory out directory
     */
    public void buildErrorCodeDoc(UnionConfig config, String outDirectory) {
        List<ApiErrorCode> errorCodeList = errorCodeDictToList(config);
        if (CollectionUtil.isEmpty(errorCodeList)) return;

        Template tpl = BeetlTemplateUtil.getByName(outDirectory + UnionConstants.UNION_ERROR_CODE_LIST_MD);
        setDirectoryLanguageVariable(config, tpl);
        tpl.binding(TemplateVariable.LIST.getVariable(), errorCodeList);
        String strTime = DateTimeUtil.long2Str(now, DateTimeUtil.DATE_FORMAT_SECOND);
        tpl.binding(TemplateVariable.CREATE_TIME.getVariable(), strTime);
        tpl.binding(UnionTemplateVariable.COPYRIGHT.getVariable(), config.getCopyright());
        FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + DocGlobalConstants.ERROR_CODE_LIST_MD);
    }


    /**
     * Generate Dictionary data
     *
     * @param config             global config
     * @param javaProjectBuilder java resource builder
     * @param outDirectory       out directory
     */
    public void buildDictionaryDataDoc(UnionConfig config, JavaProjectBuilder javaProjectBuilder, String outDirectory) {
        List<ApiDocDict> directoryList = buildDictionary(config, javaProjectBuilder);
        if (CollectionUtil.isEmpty(directoryList)) return;

        Template tpl = BeetlTemplateUtil.getByName(outDirectory + UnionConstants.UNION_DICT_LIST_MD);
        setDirectoryLanguageVariable(config, tpl);
        String strTime = DateTimeUtil.long2Str(now, DateTimeUtil.DATE_FORMAT_SECOND);
        tpl.binding(TemplateVariable.CREATE_TIME.getVariable(), strTime);
        tpl.binding(TemplateVariable.VERSION.getVariable(), now);
        tpl.binding(TemplateVariable.DICT_LIST.getVariable(), directoryList);
        tpl.binding(UnionTemplateVariable.COPYRIGHT.getVariable(), config.getCopyright());
        FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + DocGlobalConstants.DICT_LIST_MD);
    }

    /**
     * Build dictionary
     *
     * @param config             api config
     * @param javaProjectBuilder JavaProjectBuilder
     * @return list of ApiDocDict
     */
    private List<ApiDocDict> buildDictionary(ApiConfig config, JavaProjectBuilder javaProjectBuilder) {
        List<ApiDataDictionary> apiDataDictionaryList = config.getDataDictionaries();
        if (CollectionUtil.isEmpty(apiDataDictionaryList)) {
            return new ArrayList<>(0);
        }
        List<ApiDocDict> apiDocDictList = new ArrayList<>();
        try {
            int order = 0;
            for (ApiDataDictionary apiDataDictionary : apiDataDictionaryList) {
                order++;
                Class<?> clazz = apiDataDictionary.getEnumClass();
                if (Objects.isNull(clazz)) {
                    if (StringUtil.isEmpty(apiDataDictionary.getEnumClassName())) {
                        throw new RuntimeException("Enum class name can't be null.");
                    }
                    clazz = Class.forName(apiDataDictionary.getEnumClassName());
                }
                ApiDocDict apiDocDict = new ApiDocDict();
                apiDocDict.setOrder(order);
                apiDocDict.setTitle(apiDataDictionary.getTitle());
                JavaClass javaClass = javaProjectBuilder.getClassByName(clazz.getCanonicalName());
                if (apiDataDictionary.getTitle() == null) {
                    apiDocDict.setTitle(javaClass.getComment());
                }
                List<DataDict> enumDictionaryList = EnumUtil.getEnumInformation(clazz, apiDataDictionary.getCodeField(),
                        apiDataDictionary.getDescField());
                if (!clazz.isEnum()) {
                    throw new RuntimeException(clazz.getCanonicalName() + " is not an enum class.");
                }
                apiDocDict.setDataDictList(enumDictionaryList);
                apiDocDictList.add(apiDocDict);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return apiDocDictList;
    }

    public void buildSearchJs(List<ApiDoc> apiDocList, ApiConfig config, JavaProjectBuilder javaProjectBuilder, String template) {
        List<ApiErrorCode> errorCodeList = errorCodeDictToList(config);
        UnionConfig uConfig = (UnionConfig) config;
        Template tpl = BeetlTemplateUtil.getByName(template);
        // common Doc tree start
        int index = uConfig.getStartOrder();

        List<UnionApiDocGroup> apiList = Optional.ofNullable(htmlToc).map(ht -> ht.stream().map(toc -> {
            UnionApiDocGroup apiDoc = new UnionApiDocGroup(false);
            String name = RegExUtils.replaceAll(toc.getName(), REGEX_HEADER, "");
            apiDoc.setAlias(name);
            apiDoc.setName(name);
            apiDoc.setDesc(name);
            apiDoc.setOrder(toc.getOrder());
            apiDoc.setLink(name);

            List<HtmlParser.HtmlToc> children = toc.getChildren();
            if (children != null && !children.isEmpty()) {
                List<ApiMethodDoc> list = children.stream().map(m -> {
                    ApiMethodDoc apiMethodDoc = new ApiMethodDoc();
                    apiMethodDoc.setContentType("");
                    apiMethodDoc.setDesc(RegExUtils.replaceAll(m.getName(), REGEX_HEADER, ""));
                    apiMethodDoc.setOrder(m.getOrder());
                    return apiMethodDoc;
                }).collect(toList());
                apiDoc.setList(list);
            }
            return apiDoc;
        }).collect(toList())).orElse(new ArrayList<>());
        // common Doc tree end

        // doc details start
        Map<String, String> titleMap = setDirectoryLanguageVariable(config, tpl);
        UnionApiDocGroup details = new UnionApiDocGroup();
        String detailsName = titleMap.get(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable());
        details.setOrder(index + 1);
        details.setAlias(detailsName);
        details.setName(detailsName);
        details.setDesc(detailsName);
        details.setLink(detailsName);

        for (ApiDoc apiDoc1 : apiDocList) {
            UnionApiDocGroup srcApi = (UnionApiDocGroup) apiDoc1;
            if (srcApi.isDefaultGroup()) {//default api group no display
                List<ApiMethodDoc> list = srcApi.getList();
                for (ApiMethodDoc m : list) {
                    UnionApiDocGroup api2 = new UnionApiDocGroup();
                    api2.setLink(m.getDesc());
                    api2.setAlias(m.getDesc());
                    api2.setDesc(m.getDesc());
                    api2.setOrder(m.getOrder());
                    api2.setName(m.getDesc());
                    details.addChildren(api2);
                }
            } else {
                UnionApiDocGroup api = new UnionApiDocGroup();
                api.setLink(srcApi.getLink());
                api.setAlias(srcApi.getAlias());
                api.setDesc(srcApi.getDesc());
                api.setOrder(srcApi.getOrder());
                api.setName(srcApi.getName());
                List<ApiMethodDoc> list = srcApi.getList();
                for (ApiMethodDoc m : list) {
                    ApiMethodDoc apiMethodDoc = new ApiMethodDoc();
                    apiMethodDoc.setContentType("");
                    apiMethodDoc.setOrder(m.getOrder());
                    apiMethodDoc.setDesc(m.getDesc());
                    api.addListItem(apiMethodDoc);
                }
                details.addChildren(api);
            }
        }
        apiList.add(details);
        // doc details end

        // error code start
        if (CollectionUtil.isNotEmpty(errorCodeList)) {
            UnionApiDocGroup apiDoc1 = new UnionApiDocGroup(false);
            apiDoc1.setOrder(index + 2);
            apiDoc1.setDesc(titleMap.get(TemplateVariable.ERROR_LIST_TITLE.getVariable()));
            apiDoc1.setList(new ArrayList<>(0));
            apiDoc1.setLink("error_code_list");
            apiDoc1.setAlias("error");
            apiList.add(apiDoc1);
        }
        // error code end

        // dict list start
        List<ApiDocDict> apiDocDictList = buildDictionary(config, javaProjectBuilder);
        if (CollectionUtil.isNotEmpty(apiDocDictList)) {
            UnionApiDocGroup apiDoc1 = new UnionApiDocGroup(false);
            apiDoc1.setOrder(index + 3);
            apiDoc1.setLink("dict_list");
            apiDoc1.setAlias("dict");
            apiDoc1.setDesc(titleMap.get(TemplateVariable.DICT_LIST_TITLE.getVariable()));
            List<ApiMethodDoc> methodDocs = new ArrayList<>();
            for (ApiDocDict apiDocDict : apiDocDictList) {
                ApiMethodDoc methodDoc = new ApiMethodDoc();
                methodDoc.setOrder(apiDocDict.getOrder());
                methodDoc.setDesc(apiDocDict.getTitle());
                methodDocs.add(methodDoc);
            }
            apiDoc1.setList(methodDocs);
            apiList.add(apiDoc1);
        }
        // dict list end

        tpl.binding(TemplateVariable.API_DOC_LIST.getVariable(), new Gson().toJson(apiList));
        tpl.binding(UnionTemplateVariable.DOC_DETAILS_ORDER.getVariable(), uConfig.getStartOrder() + 1);
        FileUtil.nioWriteFile(tpl.render(), config.getOutPath() + FILE_SEPARATOR + SEARCH_JS_OUT);
    }

    @Override
    public Map<String, String> setDirectoryLanguageVariable(ApiConfig config, Template tpl) {
        Map<String, String> titleMap = super.setDirectoryLanguageVariable(config, tpl);
        if (Objects.nonNull(config.getLanguage())) {
            if (DocLanguage.CHINESE.code.equals(config.getLanguage().getCode())) {
                titleMap.put(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable(), UnionConstants.DOC_DETAILS_CN_TITLE);
                tpl.binding(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable(), UnionConstants.DOC_DETAILS_CN_TITLE);
            } else {
                titleMap.put(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable(), UnionConstants.DOC_DETAILS_EN_TITLE);
                tpl.binding(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable(), UnionConstants.DOC_DETAILS_EN_TITLE);
            }
        } else {
            titleMap.put(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable(), UnionConstants.DOC_DETAILS_CN_TITLE);
            tpl.binding(UnionTemplateVariable.DOC_DETAILS_TITLE.getVariable(), UnionConstants.DOC_DETAILS_CN_TITLE);
        }
        return titleMap;
    }

}
